The present invention relates to data processing and more particularly to class loading in an object oriented programming language, such as the JAVA programming language.
In the JAVA 2 environment classes are loaded only when they are used for the first time, thereby reducing memory usage and improving system response time. During runtime, the JAVA Virtual Machine (JVM) invokes one or more class loaders to load any necessary classes. Class loaders are objects that can be defined using the JAVA 2 programming language and represent instances of the class java.lang.ClassLoader. The java.lang.ClassLoader.loadClass method accepts a class name as an argument, and returns a class object that is the runtime representation of a class type. These class loaders may be user defined. For example, a user may create a class loader that is capable of providing class definitions from a remote location or to assign security attributes to a class loaded from a particular source.
A class loader needs to provide class definition data in the form of a byte array, containing valid JAVA byte code, to the Virtual Machine implementation. It is up to the specific class loader implementation how to actually do this. Some may read that data from files in the file system, some others may fetch that data remotely over a network.
A class loader L may create a class object C by defining it directly or by delegating to another class loader. If L creates C directly, we say that L defines C or, equivalently, that L is the defining loader of C. When one class loader delegates to another class loader, the loader that initiates the loading is not necessarily the same loader that completes the loading and defines the class. If L creates C, either by defining it directly or by delegation, we say that L initiates the loading of C or, equivalently, that L is an initiating loader of C.
At run time, a class is identified by a pair consisting of the name of the class and the defining loader for the class. This can be expressed by the notation C=<N,L> where C is the class, N is the name of the class, and L is the defining loader for the class. A class can also be described in terms of the initiating loader for the class using the notation C=NL where C is the class, N is the name of the class, and L is the initiating loader for the class. A class loader may initiate the loading of a class but delegate to another class loader the task of actually loading the class. For example, L1 may initiate the loading of the class named D but may delegate the responsibility for actually loading the class named D to another class loader L2. If L2 is actually capable of providing the class, we end up having DL1=DL2. If L2 is the defining loader of the class then DL1=<D,L2>=DL2.
For the sake of simplicity, we will frequently restrict our explanations to provision of class definitions, although class loaders can be used in the JAVA 2 environment to provide arbitrary other binary resources in an equivalent fashion.
FIG. 1 illustrates the tree-based class loading delegation pattern implemented by the JAVA 2 environment. Each class loader in the class hierarchy has at most one parent loader. During the lifetime of a specific loader, its parent loader is not exchanged. When a class loader is asked to provide a class definition by a call to its loadClass method (step 100) it first checks if there is a parent class loader (step 105). If there is a parent class loader for the classloader, the parent class loader is first asked for a class definition (step 110). If the parent class loader returns a class definition (step 115), that class definition is returned (step 120). If the parent class loader is unable to provide a class definition or if there is no parent class loader, the class loader tries to obtain a class definition using its own resources (step 125). If a class definition is successfully obtained (step 130), the resulting class definition is returned (step 120). If a class definition is not obtained (step 130), the caller is notified that a class definition could not be found for the named class (step 135).
FIG. 2 shows an example of a class definition hierarchy that can be defined using the JAVA 2 class delegation pattern. There are four classe loaders C1 202, C2 204, C3 206 and C4 208. The four class loaders share a common provision provided by the parent class loader C0 200. This can be implemented in a JAVA 2 environment by arranging the classes in a tree such that the class loaders C1, C2, C3, and C4 are children of the class loader C0. The JAVA 2 class delegation pattern ensures that the knowledge of the common provision propagates from the root of the tree to its leaves.
In many JAVA based framework applications, the class loader tree approach is chosen to isolate the class loader relevant resources of framework entities (for example, applications running on the framework) from each other. This helps avoid class collisions on the one hand and enables dynamic loading and unloading of framework entities on the other hand. Since the different framework entities do not interfere with each others' class loader resources, the class definitions of the one entity may be loaded and unloaded without affecting other framework entities. A common parent loader enables provision of shared definition, commonly used Application Programming Interfaces (APIs) of the respective framework, for example.
However, in more complicated framework applications it is desirable that framework entities, as understood above, make use of each other by providing shared class definitions (and other class loader relevant resources), e.g., APIs. In order to so, a class loader assigned to a specific framework entity must be able to delegate to other class loaders that provide the APIs of other framework entities. The tree-based approach, as described above, is not sufficient when targeting for a dynamic model (loading and unloading of framework entities), where publishers of shared class definitions and users of shared class definitions are not known beforehand. Also, in complex situations, the tree-based approach is not sufficient to cover all valid combinations of use-relationships.
FIG. 3 illustrates an example of a resolving dependency relationships between framework entities using tree-based class loaders, once all the dependency relations are known. The example assumes a framework that supports API providers and applications that make use of APIs. The framework includes two API providers API1 and API2 and two applications App1 and App2. Application App1 depends on API1 and API2, while application App2 depends on API1 only and has conflicts (i.e., cannot work) with API2. The dependency relation is resolved by having a root class that provides API1 300. The root class loader 300 has a child class loader 305 that provides API2. Application App1 can be set up to use both API1 and API2 by adding a child loader 315 that has the loader for API2 305 as a parent. Application App2 can be set up to use only API1 by adding a child loader 310 that has the root class loader 300 as a parent.
However, the running framework may not be aware of all the dependency relations in advance. For example, App1 may be loaded at an earlier point of time before App2 is loaded. A requirement that App2, loaded after App1, depends on API2 and has conflicts with API1 might require the framework to choose a completely different setup. For example, the framework may have to unload App1 in order to load App2.
Alternatively, assume that there is a third application, App3, that depends on API2 but would have conflicts with API1. There is no valid tree-based setup that satisfies all dependencies.